package executor;


import instrumentation.TrackerCoverage;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Array;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.ArrayList;
import java.lang.reflect.InvocationTargetException;

import classLoad.CustomClassLoader;
import operations.AssignConstant;
import operations.OperationResult;
import operations.InvokeMethod;
import operations.CreateObject;
import operations.CreateArray;
import operations.AssignPublicField;
import operations.OperationResult.Status;
import ga.Individual;
import operations.Operation;
import operations.ValuesGenerator;
import ga.GAInfo;
import model.CUT;
import model.Filter;
import model.StatementKind;
import model.ConstructorDeclared;
import model.MethodDeclared;
import model.TestClusterBuilder;
import util.GAConfig;
import util.DebugConfig;
import util.Randomness;
import model.ClassRegistry;
public class ReflectionExecutor {
	/** The internal object repository */
	private final Map<String, Object> repository;	
	private Individual individual;
	private Object result;
	private GAInfo gaInfo;
	private static final Map<Class<?>, Operation> repositorySuccessOperation=new LinkedHashMap<Class<?>, Operation>();;	
	public static  int execute(GAInfo gaInfo,Individual test) {
		ReflectionExecutor executor = new ReflectionExecutor(gaInfo,test);

		return executor.execute();
	
	}
	public static  Map<Class<?>, Operation> getRepositorySuccessOperation() {
		return repositorySuccessOperation;
	}
	public ReflectionExecutor(GAInfo gaInfo,Individual individual){
		
		this.repository = new LinkedHashMap<String, Object>();

		this.individual=individual;
		this.gaInfo=gaInfo;
	}
	public int execute() {
		long startTime=gaInfo.getStartTime();
		long TestTimeout=gaInfo.getTestTimeOut();
		long elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
		String clzToTest=gaInfo.getClzToTest().getName();
		  
		final List<Operation> initialCons  = individual.getInitialConstructors();
		for(Operation op : initialCons ) {
			if (elapsed > TestTimeout) break;
			try {
	//			System.out.println("ReflectionExecutor.execute:" + op.toString());
				if (op instanceof CreateObject) createObject(op);
				if(op instanceof AssignConstant) assignConstant(op);
				if(op instanceof CreateArray) createArray(op);

				elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
				gaInfo.setNoExecution(gaInfo.getNoExecution()+1);
			} catch(Throwable e) {
			//		System.err.println("ReflectionExecutor.execute:"+ e + op.getClass().getName() + " - " + op);
					continue;
			}
		}
//		System.out.println("\tReflectionExecutor.execute: individual ==============>" + individual.getSize());
//		if(individual.getSize()> GAConfig.TestCaseSize){
//			System.out.println("\tReflectionExecutor.execute: individual ==============>" + individual.getSize());
//			System.exit(1);
//		}
		final List<Operation> ops  = individual.getStatements();
		for (Operation op : ops) {
			if (elapsed > TestTimeout)
				break;

//			gaInfo.setNoExecution(gaInfo.getNoExecution() + 1);
//			System.out.println("ReflectionExecutor.execute:" + op.toString());
//  			if(op.toString().contains("nanoxml.StdXMLReader.stringReader"))
			System.out.println("ReflectionExecutor: "+ op.toString());    		  

			try {
				if (op instanceof CreateObject)
					createObject(op);
				if (op instanceof InvokeMethod)
					invokeMethod(op);
				if (op instanceof AssignConstant)
					assignConstant(op);
				if (op instanceof CreateArray)
					createArray(op);
				if (op instanceof AssignPublicField)
					assignField(op);

			} catch (Throwable e) {
				if (DebugConfig.executerClass)
					System.out.println("ReflectionExecutor.execute:" + e
							+ " - " + op);
				continue;
			}
			elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);

		}
		
		return 0;
	}


	/** createObject */
	private void createObject(Operation op) throws Throwable {
//		System.out.println("ReflectionExecutor.createObject : "+ op.toString());
		OperationResult opRes = (OperationResult) op.getInfo(OperationResult.KEY);
		
		CreateObject cObj=(CreateObject)op;
		  StatementKind statement = cObj.getConstructor();
		 Object[]inputs =cObj.getValues().toArray(); 
			List<Class<?>> parameterTpe=((ConstructorDeclared) statement).getInputTypes();
			Object[] initargs = new Object[parameterTpe.size()];
		 String reference=cObj.getRef();
		 Constructor<?> cons=((ConstructorDeclared) statement).getConstructor();
			for(int i=0; i<parameterTpe.size();i++){
				 Class<?> t = parameterTpe.get(i);
					if( cons.getName().startsWith("java.math")){
						initargs[i]=cObj.getValues().get(i);
						continue;
					}
				if (!Filter.isPrimitiveOrStringType(t))
					initargs[i]=(inputs[i]==null? null: get(inputs[i].toString()));
				else
					initargs[i]=cObj.getValues().get(i);
			}

			Object newObject = null;
			try {
				CustomClassLoader classLoader = gaInfo.getCustomClassLoader();
				Constructor<?> cons_ = classLoader.getConstructor(cons);
	//			newObject = cons.newInstance(initargs);
				cons_.setAccessible(true);
				newObject = cons_.newInstance(initargs);

			   }catch(InvocationTargetException invocationException){
				   Throwable exc = invocationException.getTargetException();
					if(exc instanceof IllegalArgumentException||exc instanceof NullPointerException) {
						if(opRes != null) opRes.setPreconditionError();
						return;
					}

					if(opRes != null) opRes.setExceptional(exc, null);
				   return;
			    }catch(Throwable e){
			    	return;
			       // e.printStackTrace();
			    }
			if(reference!=null && newObject!=null){
				set(reference,newObject);
				Class<?> successClass=((ConstructorDeclared) statement).getOutputType();
				if(get(successClass)==null)
					set(successClass,op);
				if(opRes != null) opRes.setSuccessful(null, newObject);
			}

	}
	private void createArray(Operation op){
		  CreateArray array=((CreateArray)op);
		  
		   Object theArray = Array.newInstance(array.getElementType(), array.getLength());
		   List<Object> inputList=array.getInputVariables();
		    for (int i = 0; i < array.getLength(); i++)
		    	if(Filter.isPrimitiveOrStringType(array.getElementType()))
		    			Array.set(theArray, i, inputList.get(i));
		    	else
		    		Array.set(theArray, i, get(inputList.get(i).toString()));

		    set(array.getReference(), theArray);
	}
	private void invokeMethod(Operation op)throws Throwable  {
	//	System.out.println("ReflectionExecutor.invokeMethod : "+ op.toString());
		final OperationResult opRes = (OperationResult) op.getInfo(OperationResult.KEY);
		InvokeMethod  	iMethod=(InvokeMethod) op;
		StatementKind statement = iMethod.getMethod();
		Object[]inputs =iMethod.getValues().toArray(); 
		final String retrun_=iMethod.getReturn();
		final  List<Class<?>> replacementObjectParam= iMethod.getReplacementObjectParam();
		final String receiver = iMethod.getThis();
		final Object baseObject;
		List<Object> values=new LinkedList<Object>();
		if(receiver != null)
			baseObject = get(receiver);
		else
			baseObject = null;


		List<Class<?>> parameterTpe=((MethodDeclared) statement).getInputTypes();
		for(int i=0; i<parameterTpe.size();i++){
			 Class<?> t = parameterTpe.get(i);
			 if(Filter.isObjectType(t) && replacementObjectParam!=null)
				t= replacementObjectParam.get(i);
			 if(parameterTpe.get(i).equals(java.lang.Class.class)){
				 inputs[i]=replacementObjectParam.get(i);
				 continue;
			 }
			if (!Filter.isPrimitiveOrStringType(t)&&inputs[i]!=null)
				inputs[i]=get(inputs[i].toString());
		}
		
		
		final Method m_ = ((MethodDeclared) statement).getMethod();
		if(baseObject == null && ((MethodDeclared) statement).isStatic())
			if(opRes != null) opRes.setPreconditionError();
	
		try {
			CustomClassLoader classLoader = gaInfo.getCustomClassLoader();
	//		this.result =m_.invoke(baseObject, inputs);
//			if(op.toString().contains(".getXMLBaseURI()"))
//				System.out.println("1");
			final Method m = classLoader.getMethod(m_);
			m.setAccessible(true);
			this.result = m.invoke(baseObject, inputs);
//			if(op.toString().contains(".getXMLBaseURI()"))
//				System.out.println("11");

			if (this.result != null){
//				if(op.toString().contains(".getXMLBaseURI()"))
//					System.out.println("2");

				set(retrun_, this.result);
//				if(op.toString().contains(".getXMLBaseURI()"))
//					System.out.println("22");

			}
			if(opRes != null) {
//				if(op.toString().contains(".getXMLBaseURI()"))
//					System.out.println("3");

				opRes.setSuccessful(baseObject, this.result);
//				if(op.toString().contains(".getXMLBaseURI()"))
//					System.out.println("33");

			}

			return;
		} catch (InvocationTargetException e) {
			if(DebugConfig.executerClass)
				System.err.println("ReflectionExcution.invokeMethod: "+op.toString() +" "+  e.getCause());

			Throwable exc = e.getTargetException();
				// precondition error
			if(exc instanceof IllegalArgumentException||exc instanceof NullPointerException) {
				if(opRes != null) opRes.setPreconditionError();
				return;
			}
				
			// a valid exception is thrown
			if(opRes != null) opRes.setExceptional(exc, baseObject);
			 return; // continues normal execution in case of exception in
		} catch (IllegalAccessException e) {
			System.err.println("Illegal access error."+ op.toString() + "\n *** ERROR: "+ e);
			System.exit(1);
		}	
	}
	private void assignConstant(Operation op)throws Throwable  {
		AssignConstant aConstant=(AssignConstant)op;
		Field field=aConstant.getValue();
	//	final Field field = ClassRegistry.singleton.getConstantField(field_);
		//Field field=aConstant.getValue();
		String reference=aConstant.getRefrence();
		if(field == null)
			set(reference, null);
		else{
			try{
				Object newObject = field.get(null);
				set(reference, newObject);
		} catch(Throwable e) {
	//		System.err.println("ReflectionExecutor.assignConstant :"+ e.getMessage());
		}

		}

	}
	private void assignField(Operation op)throws Throwable  {
		AssignPublicField aField=(AssignPublicField)op;
		Field field=aField.getField();
//		final Field field = ClassRegistry.singleton.getConstantField(field_,gaInfo);
		String reference=aField.getRefrence();
		final Object newValue= aField.getValue();
		Object classInstance=null;
		if(reference == null) return;
		classInstance=get(reference);
		if(classInstance==null)return;
		try{
			 field.setAccessible(true);
			 field.set(classInstance, newValue);
			set(reference, classInstance);
		} catch(Throwable e) {
			System.err.println("ReflectionExecutor.assignField :"+ op + " --> " +e.getMessage());
		}
	}

	private void set(String reference, Object value)  {
		if(GAConfig.DEBUG) {
			if(reference == null) {
				new NullPointerException("objRef cannot be null");
				return ;
			}
			if(repository == null) {
				new NullPointerException("repository cannot be null");
				return ;
			}
		}
		
		this.repository.put(reference, value);
	}
	private Object get(String reference)  {
		if(GAConfig.DEBUG) {
			if(reference == null) {
				new NullPointerException("objRef cannot be null");
				return null;
			}
			if(repository == null) {
				new NullPointerException("repository cannot be null");
				return null;
			}
		}
		return this.repository.get(reference);
	}
	private void set(Class<?> successClz, Operation op)  {
		 this.repositorySuccessOperation.put(successClz, op);
	}
	private Object get(Class<?> successClz)  {
		return this.repositorySuccessOperation.get(successClz);
	}
	 static final class NotCaughtIllegalStateException extends IllegalStateException {
		    private static final long serialVersionUID = -7508201027241079866L;
		    NotCaughtIllegalStateException(String msg) { super(msg); }
		  }
	
		public  static void main(String args[]) throws ClassNotFoundException,IOException {
			 final TrackerCoverage tracker=	TrackerCoverage.getTracker();

		       GAInfo     gaInfo     = new GAInfo();
				long startTime = Calendar.getInstance().getTimeInMillis();
				gaInfo.setTestTimeOut(60 * 500);
				 gaInfo.setTestTimeOut( 60 * 100000*1); ;

				gaInfo.setStartTime(startTime);
   
			Randomness.reset(20);
			 Set<Class<?>> allClasses = new LinkedHashSet<Class<?>>();
		      //get class from file
		      allClasses.addAll(TestClusterBuilder.readClassFromFile());

		      
		      TestClusterBuilder tCluster = new TestClusterBuilder(allClasses);
		      gaInfo.setTestCluster(tCluster);
		      
		      Map<String,Class<?>> classesToTest = tCluster.getClassToTest();
		      CUT cut=tCluster.getCut();
		      String clz=classesToTest.keySet().iterator().next();
		      gaInfo.setClzToTest(cut.getClazz());
		      
		      Set<Integer> branches = new LinkedHashSet<Integer>(10);
		      Map<String,Set<Integer>> classBranches= new LinkedHashMap<String,Set<Integer>>();
		      classBranches.put(clz,branches);
		      gaInfo.setClazzBranches(classBranches);
	
		      
//			   ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
//			    MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
//			    Class<?> myInstrumentedClass = classLoader.loadClass(clz,clz);
//			    gaInfo.setInstrumentedClass(myInstrumentedClass);
		        CustomClassLoader classLoader = new CustomClassLoader();
		        classLoader.setCut(clz);
		        classLoader.findClass(clz);
		        gaInfo.setCustomClassLoader(classLoader);
		      List<Operation> ops=new LinkedList<Operation>();
		      List<Operation> initialConstructors= Operation.randomGenerateInitialConstructors(tCluster);
		      ops.addAll(Operation.randomlyGenerateOperation(tCluster,50));

		      Individual individual=new Individual(initialConstructors,ops);
	    	 System.out.println(individual.toString());
	    	  ReflectionExecutor.execute(gaInfo,individual);
	    	  tracker.getCovBranches();
	    	  
	    	  System.out.println(individual.toString());
	    	  System.exit(0);
		}
	
}
